/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tcpserver;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import picshareservertest.ServerControl;
import shared.FileDownloadListener;
import shared.FileDownloadManager;
import shared.FileInfo;
import shared.FileTransferListener;
import shared.FileTransferManager;
import shared.FileUploadManager;
import shared.ServerRequest;
import shared.RequestType;
import shared.TransferStatus;

/**
 *
 * @author Eric
 */
public class ConnectionManager implements Runnable, FileDownloadListener {
    private int port;
    private boolean acceptingConnections;
    private int backlog;
    private ServerSocket serverSocket;
    //private List<Socket> sockets;
    private List<ConnectionListener> observers;
    private final Map<Socket, FileTransferManager> receiveDataThreads;
    private final Map<Socket, FileTransferManager> sendDataThreads;
    private ServerControl control;
    private Timer statusTimer;

    
    public ConnectionManager(ServerControl control, int port, int backlog)
    {
        acceptingConnections = true;
        this.control = control;
        receiveDataThreads = new HashMap<Socket, FileTransferManager>();
        sendDataThreads = new HashMap<Socket, FileTransferManager>();
        observers = new ArrayList();
        this.port = port;
        this.backlog = backlog;
        statusTimer = new Timer ();
        statusTimer.schedule(new TimerTask(){

            @Override
            public void run() {
                synchronized(sendDataThreads)
                {
                  updateTransferLists(sendDataThreads);
                }
                synchronized(receiveDataThreads)
                {
                    updateTransferLists(receiveDataThreads);
                }


            }
        }, 0, 4000);
    }


    private void updateTransferLists(Map<Socket, FileTransferManager> map)
    {
        boolean changed = false;
        for (Socket socket : map.keySet() )
        {
            if (socket.isClosed())
            {
                map.remove(socket);
                changed = true;
            }
            else
                if (map.get(socket).getStatus()==TransferStatus.COMPLETE)
                {
                    try {
                        socket.close();
                    } catch (IOException ex) {

                    }
                    map.remove(socket);

                }
        }
        if(changed)
            fireTransferListChanged(new ServerEvent(this, ""));
    }
    public void AddListener(ConnectionListener listener)
    {
        observers.add(listener);
    }

    private void fireServerErrorEvent(ServerEvent event)
    {
        for(ConnectionListener listener : observers)
            listener.serverConnectionManagerErrorOccurred(event);
    }
    private void fireServerConnectionEvent(ServerEvent event)
    {
         for(ConnectionListener listener : observers)
            listener.serverConnectionEvent(event);
    }
    private void fireTransferListChanged(ServerEvent e)
    {
        for(ConnectionListener l : observers)
            l.transferListChanged(e);
    }

   
    public List<InetAddress> getClientDownloadList()
    {
        Set<Socket> downloadSet = sendDataThreads.keySet();
        List<InetAddress> inetList = new ArrayList<InetAddress>();
        for(Socket socket : downloadSet)
        {
            inetList.add(socket.getInetAddress());
        }
        return inetList;
    }

    public List<InetAddress> getClientUploadList()
    {
        Set<Socket> uploadSet = receiveDataThreads.keySet();
        List<InetAddress> inetList = new ArrayList<InetAddress>();
        for(Socket socket : uploadSet)
        {
            inetList.add(socket.getInetAddress());
        }
        return inetList;
    }

    private void closeConnection(Socket socket)
    {

        if (socket.isConnected())
        {
            try {
                socket.close();

            } catch (IOException ex) {
               fireServerErrorEvent(new ServerEvent(this,"Error closing connection: " + ex.getMessage()));
            }
        }
        ServerEvent connectClosedEvent = new ServerEvent(this, "Connection to " + socket.getInetAddress().getAddress() + " closed.");
        fireServerConnectionEvent(connectClosedEvent);
        if (sendDataThreads.containsKey(socket))
        {
            sendDataThreads.remove(socket);
            fireTransferListChanged(new ServerEvent(this, socket.getInetAddress().getHostName() + " removed"));
        }
    }

    public void run()  {
       
        try{
            ServerEvent connectionEvent = new ServerEvent(this, "Listenting on port "+ port);            
            serverSocket = new ServerSocket(port, backlog);
            fireServerConnectionEvent(connectionEvent);
        }
        catch(IOException e)
        {
            ServerEvent errorEvent = new ServerEvent(this, "Could Not Open Server Socket");
            fireServerErrorEvent(errorEvent);
            acceptingConnections = false;
        }
        while (acceptingConnections)
        {
            try{
                Socket newSocket = serverSocket.accept();
                ServerEvent event = new ServerEvent(this,"New Connection from " + newSocket.getInetAddress().getHostAddress());
                fireServerConnectionEvent(event);
                OutputStream outStream = newSocket.getOutputStream();
                ObjectInputStream is = new ObjectInputStream(newSocket.getInputStream());
                try {
                    Object object = is.readObject();
                    ServerRequest request = (ServerRequest)object;
                    
                    ServerEvent processEvent;
                    switch (request.getType())
                    {
                        case CLIENTDOWNLOAD:
                        {
                            processEvent = new ServerEvent(this, "Tranfering file to :" + newSocket.getInetAddress().getHostAddress());
                            fireServerConnectionEvent(processEvent);
                            FileInfo info = request.getFileInfo();
                            try
                            {
                                String fname = request.getFileInfo().getFileName();
                                InputStream stream = control.getFileStream(fname);
                                FileTransferManager uploadThread = new FileUploadManager(info, stream, outStream, request.getOffset());
                                uploadThread.start();
                                sendDataThreads.put(newSocket, uploadThread);
                                fireTransferListChanged(new ServerEvent(this,"Added Client"));
                                break;
                            }
                            catch(FileNotFoundException fnfe)
                            {
                                fireServerErrorEvent(new ServerEvent(this, "File requested not found"));
                                closeConnection(newSocket);
                            }
                           
                         
                        }
                        case CLIENTUPLOAD:
                        {
                            processEvent = new ServerEvent(this, "Receiving file from :" + newSocket.getInetAddress().getHostAddress());

                            fireServerConnectionEvent(processEvent);
                            FileInfo info = request.getFileInfo();
                            FileDownloadManager downloadThread = new FileDownloadManager(info,newSocket.getInputStream());
                            downloadThread.addListener(this);
                            downloadThread.start();
                            receiveDataThreads.put(newSocket, downloadThread);
                            fireTransferListChanged(new ServerEvent(this, "Added Client"));
                            break;
                        }
                        default:
                            processEvent = new ServerEvent(this, "Invalid Request Type from : " + newSocket.getInetAddress().getAddress());
                            fireServerErrorEvent(processEvent);
                            closeConnection(newSocket);
                    }

                } catch (ClassNotFoundException ex) {
                    ServerEvent invalidCommEvent = new ServerEvent(this,"Client did not send appropriate initial communication: ");
                    fireServerErrorEvent(invalidCommEvent);
                    closeConnection(newSocket);
                } catch (ClassCastException cce) {
                    ServerEvent invalidCommEvent = new ServerEvent(this,"Client did not send appropriate initial communication: ");
                    fireServerErrorEvent(invalidCommEvent);
                    closeConnection(newSocket);
                }

            }
            catch(IOException e)
            {
                ServerEvent err = new ServerEvent(this, "Failed Connection Attempt: " + e.getMessage());
            }
        }    
    }

    public void transferStatusChanged(FileTransferManager m) {

    }

    public void downloadComplete(FileDownloadManager source) {

        control.FileReceived(source.getFileInfo(), source.getFileData());
    }


    
}
